using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace RStein.Posterous.API
{
    internal class PosterousPost : EntityWithClientStateBase<PosterousPost>, IPosterousPost, ISinglePostInfo
    {
        #region private fields                
        private List<IPosterousComment> m_newComments;        
        private List<string> m_newMediaNames;
        private List<string> m_newTags;
        private string m_title;
        private string m_body;
        private PosterousSite m_site;
        private bool m_isNowAfterSave;
        private List<MediaStreamInfo> m_mediasInfos;

        #endregion private fields
        
        #region constructors
        public PosterousPost(int id, PosterousEntityState initialState)
            : base(id, initialState)
        {
            initCommon();
        }

        
        

         public PosterousPost(PosterousSite site) : base( PosterousEntityState.New)
         {
             if (site == null)
             {
                 throw new ArgumentNullException("site");
             }

            initCommon();
            Site = site;
            Author = new PosterousAuthor
                         {
                              Name = Site.PosterousAccount.Name,
                              Email = Site.PosterousAccount.Name
                          };

             Body = string.Empty;
             Title = String.Empty;
             Url = String.Empty;
             AreCommentsEnabled = false;
             Private = false;
             PostDate = DateTime.Now;
             
         }

        #endregion constructors        
        
        #region properties
        public bool AutoPost
        {
            get; 
            set;
        }
        #endregion properties
        
        #region methods

        protected internal override void SetDirty()
        {
            if (m_site != null)
            {
                base.SetDirty();                    
                
                if (State == PosterousEntityState.Updated)
                {
                    m_site.SetDirty();    
                }
            }
            
        }
        
        protected override void SaveUpdated()
        {
            Debug.Assert(Id > 0);
            IPosterousRequestBuilder builder = getCommonSaveBuilder(GlobalConstants.URL_UPDATE_POST);            
                       
            builder.BuildFormData(GlobalConstants.SITE_REQ_POST_ID, Id.ToString());
            processPostResult(builder);

            saveComments();
            trriggerPostSaveRefresh();            
        }

        

        protected override void SaveNew()
        {
            IPosterousRequestBuilder builder = getCommonSaveBuilder(GlobalConstants.URL_NEW_POST);
            builder.BuildFormData(GlobalConstants.SITE_REQ_POST_AUTOPOST, AutoPost.ConvertToPosterousBool());
            builder.BuildFormData(GlobalConstants.SITE_REQ_POST_PRIVATE,  Private.ConvertToPosterousBool());
            builder.BuildFormData(GlobalConstants.SITE_REQ_POST_CREATE_DATE, PostDate.ToString(GlobalConstants.DATETIME_GMT_FORMAT));
            
            processPostResult(builder);
            
            trriggerPostSaveRefresh();            
        }

        private void trriggerPostSaveRefresh()
        {
            try
            {
                m_isNowAfterSave = true;
                Refresh();
            }
            finally
            {

                m_isNowAfterSave = false;
            }
        }

        private IPosterousRequestBuilder getCommonSaveBuilder(string url)
        {
            var builder = SimpleWebFactory.Instance.Create<IPosterousRequestBuilder>();
            builder.StartBuildWebRequest(url);
            m_site.AddSiteAuthInfo(builder);
            builder.BuildFormData(GlobalConstants.SITE_REQ_ID, Site.Id.ToString());
            builder.BuildFormData(GlobalConstants.SITE_REQ_POST_TITLE, Title);
            builder.BuildFormData(GlobalConstants.SITE_REQ_POST_BODY, Body);
            addTags(builder);
            addMedia(builder);
            return builder;
        }


        protected override bool OnCustomEquals(PosterousPost other)
        {
            if (!base.OnCustomEquals(other))
            {
                return false;
            }

            return other.Site.Equals(Site);
        }

        protected override int GetCustomHashCode()
        {
            return Site.GetHashCode();
        }

        private void initCommon()
        {
            Comments = m_newComments = new List<IPosterousComment>();
            Media = new List<IPosterousMedium>();
            Tags = m_newTags = new List<String>();
            m_newMediaNames = new List<string>();
            m_mediasInfos = new List<MediaStreamInfo>();
        }

        private void innerRefresh(ISinglePostInfo post)
        {
            Debug.Assert(Id == post.Id);
            AreCommentsEnabled = post.AreCommentsEnabled;
            Author = post.Author;
            Body = post.Body;

            Comments = post.Comments;
            Link = post.Link;
            Url = post.Url;
            Media = post.Media;
            PostDate = post.PostDate;
            //Private = post.Private;
            //Tags = post.Tags;
            Title = post.Title;
            Views = post.Views;                        
            clearCollections();
        }

        private string getBitlySuffix()
        {
            Uri uri = new Uri(Url);
            return uri.GetBitLySuffix();
        }

        private void processPostResult(IPosterousRequestBuilder builder)
        {
            try
            {
                SuppressChanges = true;
                var response = builder.GetResponse();
                IResponseBuilder respBuilder = new PosterousResponseBuilder();

                Dictionary<String, String> keys = respBuilder.ParseResponse(response, new SendPostResponseParser().GetResults);
                Id = Int32.Parse(keys[GlobalConstants.XML_ID_ELEMENT.ToString()]);                
                Url = keys[GlobalConstants.XML_URL_ELEMENT.ToString()];
                Link = keys[GlobalConstants.XML_URL_ELEMENT.ToString()];
                AreCommentsEnabled = Boolean.Parse(keys[GlobalConstants.XML_COMMENTSENABLED_ELEMENT.ToString()]);                
            }
            finally
            {                
                SuppressChanges = false;    
            }
            
            
        }

        private void clearCollections()
        {
            m_newTags.Clear();
            m_newMediaNames.Clear();
            m_mediasInfos.ForEach(media => media.DisposeFunction(media.Stream));
            m_mediasInfos.Clear();
            m_newComments.Clear();
        }

        private void addMedia(IPosterousRequestBuilder builder)
        {
            m_newMediaNames.ForEach(builder.BuildFormDataFromFile);
            m_mediasInfos.ForEach(media => builder.BuildFormDataFromStream(media.Stream, media.MediaName));
        }

        private void addTags(IPosterousRequestBuilder builder)
        {
            var serializer = new PostTagsValueSerializer();

            string tags = serializer.SerializeValue(m_newTags);

            if (!String.IsNullOrEmpty(tags))
            {
                builder.BuildFormData(GlobalConstants.SITE_REQ_POST_TAGS,  tags);
            }
            
        }

        private void checkObjectIsNotNew()
        {
            if (!m_isNowAfterSave && 
                State != PosterousEntityState.Loaded &&
                State != PosterousEntityState.Updated)
            {
                throw new PosterousException(GlobalConstants.INVALID_IN_STATE_NEW_EX);
            }
        }

        private void saveComments()
        {
            m_newComments.Cast<PosterousComment>().ToList().ForEach(comment => comment.SaveChanges());
            m_newComments.Clear();
        }

        private bool isAddTagAllowed()
        {
            return (State == PosterousEntityState.New);
        }

        private void checkMediaStream(Stream stream, string mediaName)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (string.IsNullOrEmpty(mediaName))
            {
                throw new ArgumentException("mediaName");
            }
        }

        #endregion methods
        #region Implementation of IPosterousPost

        public string Link
        {
            get; set;
        }

        public string Title
        {
            get
            {
                return m_title;
            }
            set
            {
                m_title = value ?? String.Empty;
                SetDirty();
            }
        }

        public string Url
        {
            get;
            set;
        }
        

        public string Body
        {
            get
            {
                return m_body;
            }
            set
            {
                m_body = value ?? String.Empty;
                SetDirty();
            }
        }

        public DateTime PostDate
        {
            get;
            set;
        }

        public int Views
        {
            get;
            set;
        }

        public bool Private
        {
            get;
            set;
        }

        public IAuthor Author
        {
            get;
            set;
        }

        public bool AreCommentsEnabled
        {
            get;
            set;
        }


        public IEnumerable<IPosterousComment> Comments
        {
            get;
            set;
        }

        public IEnumerable<IPosterousMedium> Media
        {
            get;
            set;
        }

        public IEnumerable<string> Tags
        {
            get;
            set;
        }
        public IPosterousSite Site
        {
            get
            {
                return m_site;
            }
            set
            {
                m_site = (PosterousSite) value;
            }
        }


        public virtual IPosterousComment CreateNewComment(string commentBody)
        {
            checkObjectIsNotNew();
            
            if (commentBody == null)
            {
                throw new ArgumentNullException("commentBody");
            }

            var comment = new PosterousComment(this);
            comment.Body = commentBody;
            comment.Author = new PosterousAuthor
                                 {
                                     Name = Site.PosterousAccount.Name,
                                     Email = Site.PosterousAccount.Name
                                 };
            m_newComments.Add(comment);
            Comments = Comments.Union(m_newComments).ToList();
            SetDirty();            
            return comment;
        }

       

        public virtual void AppendMedium(string filePath)
        {
            if ((String.IsNullOrEmpty(filePath)) ||
                !File.Exists(filePath))
            {
                throw new ArgumentException("fileName");
            }

            
            m_newMediaNames.Add(filePath);
            SetDirty();

        }


        public virtual void AppendMedium(Stream stream, string mediaName)
        {
            AppendMedium(stream, mediaName, null);
            
        }


        
        public virtual void AppendMedium(Stream stream, string mediaName, Action<Stream> releaseStreamFunction)
        {
            checkMediaStream(stream, mediaName);
            var mediaInfo = new MediaStreamInfo(stream, mediaName, releaseStreamFunction ?? delegate {});
            m_mediasInfos.Add(mediaInfo);
            SetDirty();
        }


        public virtual void AddTag(string tag)
        {
            if (!isAddTagAllowed())
            {
                throw new PosterousException(GlobalConstants.INVALID_IN_STATE_LOADED_EX);
            }

            if (String.IsNullOrEmpty(tag))
            {
                throw new ArgumentNullException("tag");
            }
            m_newTags.Add(tag);
            Tags = Tags.Union(m_newTags);
            SetDirty();
        }

      

        public virtual void Refresh()
        {
            
            try
            {
                SuppressChanges = true;
                
                checkObjectIsNotNew();
                string bitLysuffix = getBitlySuffix();

                var bitLyService = Site.PosterousAccount.Application.BitLyService;
                var post = bitLyService.GetSinglePost(bitLysuffix);

                innerRefresh(post);
                if (State == PosterousEntityState.Updated)
                {
                    SetLoaded();    
                }
                
                OnRefreshCompleted(new EventArgsValue<bool>(true, null));
                
            }
            finally
            {

                SuppressChanges = false;
            }
            

        }

        public virtual void RefreshAsync()
        {
            Action<Exception> exchandler = e =>
            {
                new PosterousException(GlobalConstants.ASYNC_EXCEPTION_TEXT, e);
                OnRefreshCompleted(new EventArgsValue<Boolean>(false, e));
            };

            AsyncHelper.RunDelegateAsync((Action) Refresh, exchandler, null);            
        }

        public event EventHandler<EventArgsValue<bool>> RefreshCompleted;

        protected virtual void OnRefreshCompleted(EventArgsValue<bool> e)
        {
            EventHandler<EventArgsValue<bool>> handler = RefreshCompleted;
            if (handler != null)
            {
                Thread.CurrentThread.GetCurrentSynchronizationContext().Post(o => handler(this, e), null);
            }
        }

        #endregion

        #region Inner classes

        
        #endregion Inner classes


    }
}